#include <stdio.h>
#include <math.h>
#include <iostream>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <pthread.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>

#include "opencv2/core/core.hpp"
#include "opencv2/nonfree/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"

#define IMAGE_BUFFER_SIZE 40000
#define N_CORES 8
#define MAX_CATAGORIES 64

using namespace cv;

void doAllCatagoryInDir(char*);
double distance(double,double,double,double);
double area(double, double, double);
double doHomo(int);
double initObjectVectors(int);
void *initObject(void*);
int initScene(char*);
char* serve(char*,char*);
char* search(char*, long, long);
void startServer(void);
void *doHomoThread(void*);
void initObjectsWithNCores(char*);
bool hits(char*,char*);

//image library
struct libraryEntry {
	long starts;
	long ends;
	char catagory[256];
};
long librarySize = 0;
struct libraryEntry entries[MAX_CATAGORIES];
int catagories = 0;

//cv global varibles
const int minHessian = 4000;
double sceneSize;
SurfFeatureDetector detector( minHessian );
SurfDescriptorExtractor extractor;
FlannBasedMatcher matcher;

//scene global variables
Mat scene;
std::vector<KeyPoint> keypoints_scene;
Mat descriptors_scene;

//object global variables
char imageNames[IMAGE_BUFFER_SIZE][256];
Mat object[IMAGE_BUFFER_SIZE];
std::vector<KeyPoint> keypoints_object[IMAGE_BUFFER_SIZE];
Mat descriptors_object[IMAGE_BUFFER_SIZE];

//init thread struct
struct initPara {
	int threadNum;
	char* dir;
	long ret;
};

	int main( int argc, char** argv ){
	

		if( argc != 2 )	{
			return -1; 
		}

	doAllCatagoryInDir(argv[1]);
	/*
	for(int i = 1; i<=argc-1;i++){
		
	}
	*/
		printf("Server is up \n");
		//serve("../static/images/20130323185845110_3.jpg","camerara digital");
		//serve("../static/images/20130323185845110_3.jpg","asus tablet");
		startServer();
		
		return 0;
    }
	
	int initScene(char* sceneName){
		//calculate the scene matrix, size, keypoints vector,descriptor matrix
		scene = imread( sceneName, CV_LOAD_IMAGE_GRAYSCALE );
		if( !scene.data ){ 
			std::cout<< " --(!) Error reading scene image::name "<<sceneName << std::endl; return -1;
		}
		sceneSize = scene.size().area();
		detector.detect( scene, keypoints_scene );
		extractor.compute( scene, keypoints_scene, descriptors_scene );	
		return 0;
	}

	
	void initObjectsWithNCores(char* objectDir){
		//debug:: first timer
		time_t timer1,timer2;
		time(&timer1);
	
		//split the workload onto N cores
		pthread_t thread[N_CORES+1];
		struct initPara para[N_CORES+1];
		
		for(int i = 1; i<= N_CORES;i++){
			para[i].threadNum = i - 1;
			para[i].dir = objectDir;
			pthread_create( &thread[i], NULL, initObject, &para[i]);
		}
		
		for(int i = 1;i <= N_CORES; i++){
			pthread_join( thread[i], NULL);
		}
		
		time(&timer2);
		printf("There are %ld images in the catagory %s\n",para[1].ret, objectDir);
		librarySize += para[1].ret;
		printf("%f seconds elapsed during init\n",difftime(timer2,timer1));

	}
	
	void  doAllCatagoryInDir(char* baseDir){
		DIR *dir;
		struct dirent *ent;
		char objectPath[256];
		if ((dir = opendir (baseDir)) != NULL) {
			while (((ent = readdir (dir)) != NULL)) {
				int len = strlen(ent->d_name);
				if(len <= 2) continue;
				strcpy(objectPath, baseDir);	
				strcat(objectPath, "/");
				strcat(objectPath, ent->d_name);
				
				catagories++;
				char objectDir[strlen(objectPath)];
				strcpy(objectDir, objectPath);
				
				entries[catagories].starts = librarySize + 1;
				
				char* cataName = strrchr(objectDir, '/');
				if(cataName==NULL) cataName = objectDir;
				
				strcpy(entries[catagories].catagory,cataName);
				
				initObjectsWithNCores(objectDir);
				entries[catagories].ends = librarySize;
				
				printf("catagory %s: from %ld to %ld\n", entries[catagories].catagory, entries[catagories].starts, entries[catagories].ends );
			
				//printf("%s\n",objectPath);
			
			}
		}
	
	}

	void *initObject(void* ptr){
		struct initPara* para = (struct initPara *)ptr;
	
		int threadNum = para[0].threadNum;
		char* objectDir = para[0].dir;

		
		//interate throughout the given directory
		DIR *dir;
		struct dirent *ent;
		char objectPath[256];

		if ((dir = opendir (objectDir)) != NULL) {
			long counter = librarySize;
			while (((ent = readdir (dir)) != NULL)) {

				//rule out the invalid image names
				int len = strlen(ent->d_name);
				if(len <= 4) continue;
				if(ent->d_name[len-1]!='g') continue;
				if(ent->d_name[len-2]!='p') continue;
				if(ent->d_name[len-3]!='j') continue;
				if(ent->d_name[len-4]!='.') continue;
				//counter is the index for images
				counter++;
				if(counter%N_CORES==threadNum){
					//printf("Thread %d :: %ldth-> %s\n", threadNum, counter, ent->d_name);
						if(strcmp(ent->d_name,"4791392_sb.jpg")==0) printf("Thread %d :: %ldth-> %s\n", threadNum, counter, ent->d_name);
					//assign String object with parameter, strcat with rewrite first parameter
					strcpy(objectPath, objectDir);
					//printf("dir -> %s\n", objectDir);
					
					strcat(objectPath, "/");
					strcat(objectPath, ent->d_name);
					//printf("full path -> %s\n", objectPath);
					
					//test pic "../BestbuyProducts/images/6.jpg" 0.756134
					//../BestbuyProducts/images/2121079_rc.jpg
					//calulate the object iamge matrix
					object[counter] = imread( objectPath, CV_LOAD_IMAGE_GRAYSCALE );

					if( !object[counter].data){ 
						std::cout<< " --(!) Error reading object images ::name "<< objectPath << std::endl; 
						return ptr;
					}
					strcpy(imageNames[counter],ent->d_name);
					
					initObjectVectors(counter);
				}
				
			

			}
			//close the directory
			closedir (dir);
			para[0].ret = counter - librarySize;
			
			//debug:: timer program ouputs here

			return ptr;
		} else {
		  return ptr;
		}

	}

	char* serve(char* sceneName,char* catName){
		for(int i = 1; i<=catagories; i++){
			if(hits(catName,entries[i].catagory)){
				printf("category %s hits.\n",entries[i].catagory);
				char* result = search(sceneName,entries[i].starts,entries[i].ends);
				if(result!=NULL) return result;
			}
		}
		return NULL;
	}
	
	bool hits(char* targetStr,char* objectStr){
		char* pch; 
		char temp[256];
		strcpy(temp, objectStr);
		pch = strtok(temp, "_/");

		while(pch != NULL){
			if(strstr(targetStr,pch)) return true;
			pch = strtok (NULL, "_/");
		}
		
		return false;
	
	}
	
	char* search(char* sceneName, long from, long to){
		
		//time_t timer1,timer2;
		//time(&timer1);
		pthread_t thread[N_CORES+1];
		
		long size = to - from + 1;
		
		if (initScene(sceneName)==-1){
			return NULL;
		}
		
		double para[N_CORES+1][4];
		for(int i = 1;i <= N_CORES; i++){
			para[i][0] = (i-1)*(size/N_CORES)+from;
			para[i][1] =  i*(size/N_CORES)+from;
			if(i==N_CORES) para[i][1] = to;
//			printf("form for %d is %f, to is %f\n",i,para[i][0],para[i][1]);
		}
		
		for(int i = 1;i <= N_CORES; i++){
			pthread_create( &thread[i], NULL, doHomoThread, (void*) para[i]);
		}
		
		for(int i = 1;i <= N_CORES; i++){
			pthread_join( thread[i], NULL);
		}
/*		
		//thread1
		double para1[4];
		para1[0] = 1;
		para1[1] = librarySize/2;
		
		//thread2
		double para2[4];
		para2[0] = librarySize/2+1;
		para2[1] = librarySize;
		
		//start threads
		pthread_create( &thread1, NULL, doHomoThread, (void*) para1);
		pthread_create( &thread2, NULL, doHomoThread, (void*) para2);
		
		pthread_join( thread1, NULL);
		pthread_join( thread2, NULL);
*/	
		//combine return values from threads
		double min = 100;
		char* name;
		int pos = 1;
		
		for(int i = 1;i <= N_CORES; i++){
			if(min > para[i][2]){
				min = para[i][2];
				pos = (int) para[i][3];
			}
		}
/*		
		double min1 = para1[2];
		double min2 = para2[2];
		int pos1 = (int) para1[3];
		int pos2 = (int) para2[3];
		if(min1 < min2){
			min = min1;
			name = imageNames[pos1];
		} else {
			min = min2;
			name = imageNames[pos2];
		}
*/
		name = imageNames[pos];
	
		//final result
		if(min<100) {
			printf("The image named %s has a minimum distance of %f\n", name, min);
			return name;
		} else {
			return NULL;
		}
		

		
		//debug:: comparision time
		//time(&timer2);
		//printf("%f seconds elapsed during serve\n",difftime(timer2,timer1));
		
	}
	
	void *doHomoThread( void *ptr ){
		 int from,to;
		 double* para = (double *) ptr;
		 from = (int) para[0];
		 to = (int) para[1];
		 
		 double rate, min = 100;
		 int pos = from;
		 for(int j = from;j < to;j++){
			
//			printf("%dth object name:%s\n",j,imageNames[j]);
//			printf("   keypoint size:%u\n",(unsigned)keypoints_object[j].size());
			
			//record the file name with largest similarity
			rate = doHomo(j);

			//debug:: print out similarity and file name for each image
			//printf("%d: %f : %s\n", i, rate, ent->d_name );
			
			if(rate >= 0 && rate < min) {
				min = rate;
				pos = j;
			}
			
		}
//		printf("%f : %s\n", min, imageNames[pos] );
		para[2] = min;
		para[3] = pos;
		return ptr;
	}
	
	double doHomo(int i){
//		printf("Matching the %dth object -->",i);
		
		//Create matches
		std::vector< DMatch > matches;
		matcher.match( descriptors_object[i], descriptors_scene, matches );
		
		//-- Quick calculation of max and min distances between keypoints
		double max_dist = 0; 
		double min_dist = 100;

		for( int j = 0; j < descriptors_object[i].rows; j++ ){ 
			double dist = matches[j].distance;
			if( dist < min_dist ) min_dist = dist;
			if( dist > max_dist ) max_dist = dist;
		}

//		printf("min is %f\n", min_dist);
//		printf("max is %f\n", max_dist);
		
		std::vector< DMatch > good_matches;
		for( int j = 0; j < descriptors_object[i].rows; j++ ){
			if( matches[j].distance <= 3*min_dist ){
				good_matches.push_back( matches[j]); 
			}
		}
		
//		printf("good match size is %u\n", (unsigned)good_matches.size());
		if(good_matches.size()<4) return -1;

		//-- Localize the object
		std::vector<Point2f> objectPoints;
		std::vector<Point2f> scenePoints;
		
		for( int j = 0; j < good_matches.size(); j++ ){
			//-- Get the keypoints from the good matches
			objectPoints.push_back( keypoints_object[i][ good_matches[j].queryIdx ].pt );
			scenePoints.push_back( keypoints_scene[ good_matches[j].trainIdx ].pt );
		}
		
		Mat H = findHomography( objectPoints, scenePoints, CV_RANSAC );

		//-- Get the corners from the scene
		static std::vector<Point2f> obj_corners(4);
		obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( object[i].cols, 0 );
		obj_corners[2] = cvPoint( object[i].cols, object[i].rows ); obj_corners[3] = cvPoint( 0, object[i].rows );
		std::vector<Point2f> scene_corners(4);
		
		perspectiveTransform( obj_corners, scene_corners, H);
		
		double a = distance(scene_corners[0].x,scene_corners[0].y,scene_corners[1].x,scene_corners[1].y);
		double b = distance(scene_corners[1].x,scene_corners[1].y,scene_corners[2].x,scene_corners[2].y);
		double c = distance(scene_corners[2].x,scene_corners[2].y,scene_corners[0].x,scene_corners[0].y);
		double d = distance(scene_corners[2].x,scene_corners[2].y,scene_corners[3].x,scene_corners[3].y);
		double e = distance(scene_corners[3].x,scene_corners[3].y,scene_corners[0].x,scene_corners[0].y);
		double f = distance(scene_corners[3].x,scene_corners[3].y,scene_corners[1].x,scene_corners[1].y);
		double g = area(a,b,c)+area(d,e,c);
		double g1 = area(a,f,e)+area(b,d,f);
		double h = g/(double)sceneSize;

//		printf("%f,%f,%f\n",g,g1,h);
		if((h>0.4)&&((h<1)||abs(h-1)<0.000001)&&abs(g - g1)< 0.1){
			return min_dist;
		} else {
			return -1;
		}
	}
	
	double initObjectVectors(int i){
//		printf("initializing the %dth object\n",i);
		detector.detect( object[i], keypoints_object[i]);
		extractor.compute( object[i], keypoints_object[i], descriptors_object[i] );
		return 0;
	}
	
	
	double distance(double x1,double y1,double x2,double y2){
		return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
	}

	double area(double a, double b, double c){
		double s = (a + b + c)/2;

		return sqrt(s*(s-a)*(s-b)*(s-c));

	}

	void startServer(void){
		int listenfd = 0, connfd = 0;
		struct sockaddr_in serv_addr; 

		char sendBuff[1025];
		time_t ticks; 

		listenfd = socket(AF_INET, SOCK_STREAM, 0);
		memset(&serv_addr, '0', sizeof(serv_addr));
		memset(sendBuff, '0', sizeof(sendBuff)); 

		serv_addr.sin_family = AF_INET;
		serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		serv_addr.sin_port = htons(5000); 

		bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 

		listen(listenfd, 10); 

		
		char message[256], sceneName[256], catName[256];
		
		bool quit = false;
		
		while(!quit)
		{
			connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 
			
			read(connfd, message, 256);
			if(0!=strcmp(message,"\3")){
				if(0==strcmp(message,"quit")){
					quit = true;
				} else {
					char* pch = strtok(message, ",");
					strcpy(sceneName,pch);
					pch = strtok(NULL, ",");
					strcpy(catName,pch);
					printf("Matching image : %s with category name: %s\n", sceneName, catName);
					char* result =  serve(sceneName, catName);
					if(result==NULL){
						snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n","fail");
					} else {
						snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n",result);
					}
					
					printf("Finish Matching image : %s -> %s\n", sceneName, sendBuff);
					write(connfd, sendBuff, strlen(sendBuff));
				}
			}

			close(connfd);
			sleep(1);
		}
		
	}
	